/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package controller;

import java.util.ArrayList;
import java.util.HashMap;
import model.TabellenSpalte;
import model.Tupel;

/**
 *
 * @author Max
 */
public class EntropieRechner {

    private TabellenSpalte spalte;

    private TabellenSpalte zielSpalte;

    public EntropieRechner(TabellenSpalte spalte, TabellenSpalte zielSpalte) {
        this.spalte = spalte;
        this.zielSpalte = zielSpalte;
    }

    public double calcEntropie() {
        // Anzahl der Zeilen
        int anzahlZeilen = (spalte.getDaten().length - 1);
        // Liste fuer das Abgleichen der Spalten-Elemente
        ArrayList<String> spaltenElemente = new ArrayList<>();
        // Liste fuer das Abgleichen der Tupel
        ArrayList<Tupel> tupelListe = new ArrayList<>();
        // HashMap fuer das Abgleichen der Anzahl der Hauptelemente
        HashMap<String, Integer> spaltenElementenMap = new HashMap<>();
        // Boolean ob Spalte nur stetige Attribute enthaelt
        boolean stetig = true;
        for (int i = 1; i <= anzahlZeilen; i++) {
            // Pruefe ob das Element keiner Zahl enspricht
            if (spalte.getDaten()[i].matches("-?\\d+(\\.\\d+)?") == false) {
                stetig = false;
            }
            // Gucke ob das Spalten-Element schon vorhanden ist
            if (spaltenElemente.contains(spalte.getDaten()[i])) {
                // Wenn schon vorhanden, erhoehe Haeufigkeit um 1
                spaltenElementenMap.put(spalte.getDaten()[i], (Integer) (spaltenElementenMap.get(spalte.getDaten()[i]) + 1));
            } else {
                // Wenn noch nicht vorhanden, setze Haeufigkeit auf 1
                spaltenElemente.add(spalte.getDaten()[i]);
                spaltenElementenMap.put(spalte.getDaten()[i], (Integer) (1));
            }
            // Erstellen ein Tupel aus Spalten-Element und Ziel-Element
            String[] tupelDaten = {spalte.getDaten()[i], zielSpalte.getDaten()[i]};
            Tupel tupel = new Tupel(tupelDaten);
            // Gucke ob das Tupel schon vorhanden ist
            if (tupelListe.contains(tupel)) {
                // Wenn schon vorhanden, erhoehe Haeufigkeit um 1
                tupelListe.get(tupelListe.indexOf(tupel)).increase();

            } else {
                // Wenn noch nicht vorhanden, setze Haeufigkeit auf 1
                tupel.setHaeufigkeit(1);
                tupelListe.add(tupel);
            }
        }
        // Wenn nach der Iteration die Spalte stetig ist
        // dann berechne die Klassen des stetigen Attributes
        if (stetig == true) {
            calcCases(tupelListe);
            spalte.setStetig(stetig);
            return 0.0;
        }
        spalte.setSpaltenElemente(spaltenElemente);
        double entropie = 0.0;
        // Durchgehe die untersch. Elemente der Spalte
        for (int i = 0; i < spaltenElemente.size(); i++) {
            double tempEntropie = 0;
            for (int j = 0; j < tupelListe.size(); j++) {
                // Wenn das Spalten-Element dem ersten Element des Tupels entspricht
                // dann berechne die Teil-Entropie
                if (spaltenElemente.get(i).equals(tupelListe.get(j).getDaten()[0])) {
                    double tempTupelHaeufigkeit = tupelListe.get(j).getHaeufigkeit();
                    double tempSpaltenHaeufigkeit = spaltenElementenMap.get(spaltenElemente.get(i));
                    double subEntropie = tempTupelHaeufigkeit / tempSpaltenHaeufigkeit;
                    tempEntropie = tempEntropie - (subEntropie * Math.log(subEntropie) / Math.log(2));
                }
            }
            entropie = entropie + (tempEntropie * (double) spaltenElementenMap.get(spaltenElemente.get(i)) / (double) anzahlZeilen);
        }
        // Setze Anzahl an Fallunterscheidungen und geben Entropie zurueck
        spalte.setCases(spaltenElemente.size());
        spalte.setTupelListe(tupelListe);
        return entropie;
    }

    private void calcCases(ArrayList<Tupel> tupelListe) {
        // Sortierte Liste
        ArrayList<Tupel> sortierteListe = new ArrayList<>();
        // Gehe Liste durch
        for (int i=0; i < tupelListe.size(); i++) {
            // Beim 1. Objekt setze es als 1. Objekt der sortierten Liste
            if (i == 0) {
                sortierteListe.add(tupelListe.get(i));
            } else {
                // Ansonsten gehe Liste durch und gucke, ob Tupel kleiner 
                // als ein bereits vorhandenes Objekt
                // Boolean fuer die Ueberpruefung
                boolean istKleiner = false;
                for (int j=0; j < i; j++) {
                    if (Double.parseDouble(tupelListe.get(i).getDaten()[0]) <= Double.parseDouble(sortierteListe.get(j).getDaten()[0])) {
                        sortierteListe.add(j, tupelListe.get(i));
                        istKleiner = true;
                        j = i;
                    }
                }
                // Wenn Objekt nicht kleiner, dann setze es ans Ende
                if (istKleiner == false) {
                    sortierteListe.add(tupelListe.get(i));
                }
            }
        }
        // Liste der auf ein Zielelement pro Spaltenelement normalisierten Tupel
        ArrayList<Tupel> normListe = new ArrayList<>();
        // Liste für die späteren Ergebnisse
        ArrayList<Tupel> finalListe = new ArrayList<>();
        // temporäre Strings für die Überprüfung
        String tempSpaltenElement=null;
        String tempZielElement=null;
        // temporäre Häufigkeit
        int tempP=0;
        for (int i=0; i < sortierteListe.size(); i++) {
            // Wenn 1. Element, merke Daten
            if (i == 0) {
                tempSpaltenElement = sortierteListe.get(i).getDaten()[0];
                tempZielElement = sortierteListe.get(i).getDaten()[1];
                tempP = sortierteListe.get(i).getHaeufigkeit();
            }
            // Wenn Ziel-Element anders und Häufigkeit höher, setze neues
            // temporäres Ziel-Element und neue Häufigkeit
            else if (tempSpaltenElement.equals(sortierteListe.get(i).getDaten()[0]) && tempP <= sortierteListe.get(i).getHaeufigkeit()) {
                tempZielElement = sortierteListe.get(i).getDaten()[1];
                tempP = sortierteListe.get(i).getHaeufigkeit();
            } 
            else {
                // Ansonsten erstelle neues Tupel und adde es zur Liste
                String[] tempDaten = {tempSpaltenElement, tempZielElement};
                normListe.add(new Tupel(tempDaten));
                // Dann setze temporäre Daten neu
                tempSpaltenElement = sortierteListe.get(i).getDaten()[0];
                tempZielElement = sortierteListe.get(i).getDaten()[1];
                tempP = sortierteListe.get(i).getHaeufigkeit();
            }
        }
        // Erstelle letztes Tupel und füge es der Liste hinzu
        String[] tempDaten = {tempSpaltenElement, tempZielElement};
        normListe.add(new Tupel(tempDaten));
        // "Cleare" die sortierte Liste um Speicher freizugeben
        sortierteListe.clear();
        // Temporärer Wert für das arithm. Mittel
        double tempMittelwert=0;
        double tempVorherigerMittelwert=0;
        // Temporärer Wert für die untere Grenze des Mittelwertes
        double tempUntereGrenze = 0.0;
        //Temporärer boolean
        boolean ersterKnoten = true;
        // Durchgehe die normalisierte Liste
        if (normListe.size() == 1) {
            finalListe = normListe;
        } else {
        for (int i=0; i < normListe.size(); i++) {
            // Setze das temporäre Zielelement beim 1. Element
            if (i == 0) {
                tempZielElement = normListe.get(i).getDaten()[1];
                tempUntereGrenze = Double.parseDouble(normListe.get(i).getDaten()[0]);
            }
            // Wenn sich das Zielelement unterscheidet bilde
            // arithm. Mittel der beiden Spaltenwerte
            else if (tempZielElement.equals(normListe.get(i).getDaten()[1]) == false) {
                tempVorherigerMittelwert = tempMittelwert;
                tempMittelwert = (tempUntereGrenze + Double.parseDouble(normListe.get(i).getDaten()[0])) / 2;
                // Erstelle Knoten und füge ihn der Liste hinzu
                // Wenn es der erste Knoten ist, lasse Untergrenze offen
                if (ersterKnoten && i == (normListe.size()-1)) {
                    String[] daten = {"<=" + tempMittelwert, tempZielElement};
                    finalListe.add(new Tupel(daten));
                    ersterKnoten = false;
                    tempZielElement = normListe.get(i).getDaten()[1];
                    String[] datenNeu = {">" + tempMittelwert, tempZielElement};
                    finalListe.add(new Tupel(datenNeu));
                } 
                else if (ersterKnoten && i != (normListe.size()-1)) {
                    String[] daten = {"<=" + tempMittelwert, tempZielElement};
                    finalListe.add(new Tupel(daten));
                    ersterKnoten = false;
                }
                else {
                    String[] daten = {">" + tempVorherigerMittelwert + ", <=" + tempMittelwert, tempZielElement};
                    finalListe.add(new Tupel(daten));
                    if (i == (normListe.size()-1)) {
                        tempZielElement = normListe.get(i).getDaten()[1];
                        String[] datenNeu = {">" + tempMittelwert, tempZielElement};
                        finalListe.add(new Tupel(datenNeu));
                    }
                }
                tempZielElement = normListe.get(i).getDaten()[1];
                tempUntereGrenze = Double.parseDouble(normListe.get(i).getDaten()[0]);
            }
            // Wenn Zielwert gleich und letztes Tupel dann füge letzten Knoten hinzu
            else if (i == (normListe.size()-1)) {
                tempZielElement = normListe.get(i).getDaten()[1];
                String[] datenNeu = {">" + tempMittelwert, tempZielElement};
                finalListe.add(new Tupel(datenNeu));
            }
            // Wenn Zielwert gleich, dann setzte untere Grenze neu
            else {
                tempUntereGrenze = Double.parseDouble(normListe.get(i).getDaten()[0]);
            }
        }
        }
        // TEST
        for (int i=0; i < finalListe.size(); i++) {
            System.out.println(finalListe.get(i).getDaten()[0] + " " + finalListe.get(i).getDaten()[1]);
        }
        spalte.setCases(finalListe.size());
        ArrayList<String> klassenListe = new ArrayList<>();
        for (int i=0; i < finalListe.size(); i++) {
            klassenListe.add(finalListe.get(i).getDaten()[0]);
        }
        spalte.setSpaltenElemente(klassenListe);
        spalte.setTupelListe(finalListe);
    }

}
